<?php
/**
 * licenses : MIT
 * creatTime: 2020/5/16
 * author : weblinuxgame
 * filename: Token.php
 */

namespace App\Models;

use ArrayAccess;
use Predis\Client;
use Ramsey\Uuid\Uuid;
use Redis as RedisClient;
use App\Core\Enums\Define;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Redis;
use Illuminate\Contracts\Support\Arrayable;

/**
 * Class Token
 * @package App\Models
 */
class Token
{
    protected $data = [];
    protected $options = [];

    protected $primaryKey = 'id';
    protected $alias = ['id' => 'uid'];
    protected $key = '';

    protected $callbacks = [];

    const ConnectionName = 'token';
    const ExpireTime = Define::AuthTokenRememberTimeOut;

    /**
     * Token constructor.
     * @param array $data
     * @param array $options
     */
    public function __construct($data, $options = [])
    {
        $this->data = $data;
        $this->options = $options;
    }

    /**
     * 初始化
     */
    protected function initialize()
    {
        if (empty($this->options)) {
            return;
        }
        if (isset($this->options['alias']) && is_array($this->options['alias'])) {
            $this->alias = array_merge($this->alias, $this->options['alias']);
            unset($this->options['alias']);
        }
        if (isset($this->options['callbacks']) && is_array($this->options['callbacks'])) {
            $this->callback = $this->options['callbacks'];
            unset($this->options['callbacks']);
        }
    }

    /**
     * @return string
     */
    public function toString(): string
    {
        if (empty($this->key)) {
            $this->key = md5(Uuid::uuid4()->getHex()->toString() . $this->hash());
            $this->store($this->key, $this->value());
        }
        return $this->key;
    }

    /**
     * 检查数据是否正常
     * @return bool
     */
    public function check(): bool
    {
        $data = $this->toArray();
        if (isset($data[$this->primaryKey])) {
            $key = $this->alias[$this->primaryKey] ?? '';
            if (!empty($key) && !isset($data[$key])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 数据hash
     * @return string
     */
    protected function hash(): string
    {
        return md5(json_encode($this->data));
    }

    /**
     * 存储
     * @param string $key
     * @param string $value
     * @param int $expire
     * @return bool
     */
    public function store(string $key, string $value, int $expire = 0)
    {
        if (empty($expire)) {
            $expire = $this->option('expire', self::ExpireTime);
        }
        if (self::storage()->setex($key, (int)$expire, $value)) {
            $this->callback();
            return true;
        }
        return false;
    }

    /**
     * 存储之后
     */
    protected function callback()
    {
        if (isset($this->callbacks['store']) && is_callable($this->callbacks['store'])) {
            $handler = $this->callbacks['store'];
            $handler($this);
        }
    }

    /**
     * 参数
     * @param string $key
     * @param null $default
     * @return mixed|null
     */
    public function option(string $key, $default = null)
    {
        if (!isset($this->options[$key])) {
            return $default;
        }
        return $this->options[$key];
    }

    /**
     * 值
     * @return string
     */
    public function value(): string
    {
        if (empty($this->data)) {
            return '';
        }
        $data = $this->toArray();
        if (!isset($data['expired_at'])) {
            $data['expired_at'] = $this->option('expire', self::ExpireTime) + time();
        }
        // 别名
        foreach ($this->alias as $key => $alias) {
            if (isset($data[$key])) {
                $data[$alias] = $data[$key];
            }
        }
        return json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 输出
     * @return array
     */
    public function toArray(): array
    {
        $data = $this->data;
        if (!is_array($data)) {
            if ($data instanceof Arrayable) {
                $data = $data->toArray();
            }
            if ($data instanceof ArrayAccess) {
                $tmp = [];
                foreach ($data as $key => $value) {
                    $tmp[$key] = $value;
                }
                $data = $tmp;
            }
            $this->data = $data;
        }
        return (array)$data;
    }

    /**
     * @return RedisClient
     */
    public static function storage()
    {
        $conn = Redis::connection(self::ConnectionName);
        $client = $conn->client();
        if (!empty($client) && $client instanceof Client) {
            $client->getOptions()->prefix->setPrefix(config('database.redis.token.prefix', 'token_'));
        }
        return $client;
    }

    /**
     * 获取数据
     * @param string|null $key
     * @return array|mixed|null
     */
    public function data(string $key = null)
    {
        if (empty($this->data)) {
            if (!empty($this->key)) {
                $data = self::storage()->get($this->key);
                if (!empty($data)) {
                    $this->data = json_decode($data, true);
                }
            }
        }
        if (empty($key)) {
            return $this->data;
        }
        if (is_array($this->data) || $this->data instanceof ArrayAccess) {
            return Arr::get($this->data, $key);
        }
        return null;
    }

    /**
     * 验证
     * @param string $token
     * @return bool
     */
    public static function verify(string $token): bool
    {
        $data = self::storage()->get($token);
        if (empty($data)) {
            return false;
        }
        return true;
    }

    /**
     * 加载token
     * @param string $token
     * @return Token|null
     */
    public static function load(string $token): ?Token
    {
        $data = self::storage()->get($token);
        if (empty($data)) {
            return null;
        }
        $oToken = new static(json_decode($data, true));
        $oToken->key = $token;
        return $oToken;
    }

    /**
     * @return string|int
     */
    public function id()
    {
        $value = $this->data($this->primaryKey);
        if (!empty($value)) {
            return $value;
        }
        if (isset($this->alias[$this->primaryKey])) {
            $key = $this->alias[$this->primaryKey];
            return $this->data($key);
        }
        return null;
    }

}
